package edu.hawaii.ics211.exam;

import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Scanner;

/**
 * Checks if the answer that a student provided is correct.
 * This uses JUnit testing to determine if the code written meets the requirements.
 * 
 * @author Branden Ogata
 *
 */

public class CheckAnswer
{
  /**
   * The total number of points earned thus far on the exam as a whole.
   * 
   */
  private static double totalPoints = 0;
  
  /**
   * Tests the given code using the given JUnit test.
   * This will require creating files, which should be outside of the JAR in a temp folder.
   * If the name of the file to create is not provided, then this method bases the name on the
   * name of the tester class; since the tester class should start with "Test" removing the "Test"
   * from the beginning should give the name of the class to test.
   * 
   * @param code The String containing the code to test.
   * @param tester The String containing the path to the tester file.
   * @param points The double equal to the number of points that this question is worth.
   * 
   * @return evaluation The String containing the description of how the student did.
   *                    This should include the score and any comments on the testing results.
   * 
   */
  
  public static String checkAnswer(String code, String tester, double points)
  {
    if (tester == null)
    {
      return "";
    }
    else
    {
      return checkAnswer(tester.substring(4), code, tester, points);
    }
  }
  
  /**
   * Tests the given code using the given JUnit test.
   * This will require creating files, which should be outside of the JAR in a temp folder.
   * 
   * @param filename The String containing the name of the class for the user answer.
   * @param code The String containing the code to test.
   * @param tester The String containing the path to the tester file.
   * @param points The double equal to the number of points that this question is worth.
   * 
   * @return evaluation The String containing the description of how the student did.
   *                    This should include the score and any comments on the testing results.
   * 
   */
  
  public static String checkAnswer(String filename, String code, String tester, double points)
  {
    String evaluation = "";

    try
    {
      // Create and save a file with the code
      makeAnswerFile(filename, code);
      
      // Compile the tester file (which should in turn compile the answer file)
      Process compileTest = Runtime.getRuntime().exec("javac exam/" + tester + ".java");
      String compileErrors = inputStreamToString(compileTest.getErrorStream());
      
      // Execute the tester file if everything compiled correctly
      if (compileErrors.isEmpty())
      {
        Process runTest = Runtime.getRuntime().exec("java exam/" + tester + " " + points);
        evaluation = inputStreamToString(runTest.getInputStream()).trim();
        
        // Update the total points        
        // If no total is provided
        if ((evaluation.endsWith("points]")) || (evaluation.endsWith("points]\n")))
        {
          Scanner grader = new Scanner(evaluation);
          double grade = 0;
          
          try
          {
            String line = "";
            
            while (grader.hasNextLine())
            {
              line = grader.nextLine();
              grade += Double.valueOf(line.substring(line.lastIndexOf('[') + 1, 
                                                     line.lastIndexOf('/')));
            }
          }
          catch (NumberFormatException e)
          {
            evaluation += "\n";
            evaluation += "Grading failed; giving full credit\n";              
            grade = points;
          }

          totalPoints += grade;
        }
        // Else get the total at the end of the output
        else
        {
          double grade = points;
          
          try
          {
            String lastLine = evaluation.substring(evaluation.lastIndexOf('['));
            grade = Double.valueOf(lastLine.substring(1, lastLine.indexOf('/')));
          }
          catch (NumberFormatException e)
          {
            evaluation += "\n";
            evaluation += "Grading failed; giving full credit\n";
          }
          
          totalPoints += grade;
        }
        
        evaluation += inputStreamToString(runTest.getErrorStream()).trim();
      }
      else
      {
        evaluation = compileErrors;
      }
    }
    catch (IOException e)
    {
      evaluation = "There was an error in writing to a file.";
    }    
    
    return evaluation;
  }
  
  /**
   * Returns a File with the given file name composed of the given text.
   * 
   * @param filename The String containing the name of the class to create.
   * @param code The String containing the code to place in the file.
   * 
   * @throws IOException If there is an error with the answer file.
   * 
   */
  
  private static void makeAnswerFile(String filename, String code) throws IOException
  {
    PrintWriter writer = new PrintWriter(new FileWriter("exam/" + filename + ".java", false));
    String answerClass = "";
    
    // Add the package name, class name, and opening curly brace
    answerClass += "package exam;\n";
    answerClass += "\n";
    answerClass += "import java.util.*;\n";
    answerClass += "\n";
    answerClass += "public class " + filename + "\n";
    answerClass += "{\n";
    
    // Add the code
    answerClass += code + "\n";
    
    // Add the closing curly brace
    answerClass += "}\n";
    
    writer.println(answerClass);
    writer.close();
  }
  
  /**
   * Gets the text from an InputStream.
   * 
   * @param stream The InputStream to get text from.
   * 
   * @return A String containing the text that is in the parameter InputStream.
   * 
   */
  
  public static String inputStreamToString(InputStream stream)
  {
    Scanner scan = new Scanner(stream).useDelimiter("\\A");
    return (scan.hasNext()) ? (scan.next()) : ("");
  }

  /**
   * Returns the total points obtained thus far in the exam.
   *
   * @return totalPoints The double containing the total points on this exam.
   *
   */
  
  public static double getTotalPoints()
  {
    return totalPoints;
  }  
  
  /**
   * Resets the total number of points for the exam.
   * 
   */
  
  public static void resetPoints()
  {
    totalPoints = 0;
  }
}
